Appearance
生成单个模块增量代码识别 jacoco 的文件的覆盖率报告文件
#!/usr/bin/env bash
set -euo pipefail
# ===== 合并版本:虚拟环境 + diff-cover 分析脚本 =====# 特点:单文件,用完即抛,不修改工程代码
# ===== 配置参数 =====MODULES=${MODULES:-"soms-service-start"} # 多个模块用逗号分隔,为空时自动检测
THRESHOLD=${THRESHOLD:-100} # 覆盖率阈值
OUTPUT_DIR=${OUTPUT_DIR:-"./reports"} # 报告输出目录
SKIP_TESTS=${SKIP_TESTS:-0} # 跳过测试执行检查,使用现有报告: 1=跳过
FORCE_ANALYSIS=${FORCE_ANALYSIS:-0} # 强制分析: 1=强制
BASE_BRANCH=${BASE_BRANCH:-""} # 基准分支
SKIP_BRANCH_CHECK=${SKIP_BRANCH_CHECK:-0} # 跳过远程分支比对: 1=跳过
# 虚拟环境相关配置
VENV_DIR=${VENV_DIR:-"./temp_venv"} # 临时虚拟环境目录
CLEANUP_VENV=${CLEANUP_VENV:-1} # 执行后是否清理虚拟环境: 1=清理
# ===== 颜色定义 =====RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# ===== 日志函数 =====log_info() {
echo -e "${BLUE}ℹ️ $1${NC}" >&2
}
log_success() {
echo -e "${GREEN}✅ $1${NC}" >&2
}
log_warning() {
echo -e "${YELLOW}⚠️ $1${NC}" >&2
}
log_error() {
echo -e "${RED}❌ $1${NC}" >&2
}
# ===== 检查环境依赖 =====check_environment() {
log_info "检查环境依赖..."
# 检查必要工具
for tool in git python3 bc; do
if ! command -v "$tool" &> /dev/null; then
log_error "$tool 未安装或不在PATH中"
log_info "请安装 $tool 后重试"
exit 1
fi
done log_success "环境检查通过"
}
# ===== 虚拟环境管理 =====create_temp_venv() {
if [-d "$VENV_DIR"](); then
if ["$CLEANUP_VENV" == "1"](); then
log_info "发现已存在的虚拟环境,清理后重建: $VENV_DIR"
rm -rf "$VENV_DIR"
else
log_info "发现已存在的虚拟环境,复用现有环境: $VENV_DIR"
# 检查现有环境是否可用
if [-f "$VENV_DIR/bin/activate"](); then
log_success "复用现有虚拟环境"
return 0
else
log_warning "现有虚拟环境不完整,重建..."
rm -rf "$VENV_DIR"
fi
fi fi log_info "创建临时虚拟环境: $VENV_DIR"
# 创建虚拟环境
if python3 -m venv "$VENV_DIR" >/dev/null 2>&1; then
log_success "虚拟环境创建成功"
else
log_error "虚拟环境创建失败"
exit 1
fi
}
setup_venv() {
log_info "激活虚拟环境并安装依赖..."
# 激活虚拟环境
source "$VENV_DIR/bin/activate"
log_success "虚拟环境已激活"
# 检查diff-cover是否已安装
if python -c "import diff_cover" 2>/dev/null; then
log_success "diff-cover已安装,跳过安装步骤"
return 0
fi
# 升级pip
log_info "升级pip..."
pip install --upgrade pip >/dev/null 2>&1
# 安装diff-cover
log_info "安装diff-cover..."
if pip install diff-cover >/dev/null 2>&1; then
log_success "diff-cover安装完成"
else
log_error "diff-cover安装失败"
exit 1
fi
# 验证安装
if python -c "import diff_cover" 2>/dev/null; then
log_success "依赖验证通过"
else
log_error "依赖验证失败"
exit 1
fi
}
cleanup_venv() {
if ["$CLEANUP_VENV" == "1" && -d "$VENV_DIR"](); then
log_info "清理临时虚拟环境: $VENV_DIR"
rm -rf "$VENV_DIR"
log_success "临时虚拟环境已清理"
fi
}
# ===== 验证分支是否存在 =====validate_branch() {
local branch="$1"
local branch_type="$2"
if git show-ref --verify --quiet "refs/remotes/$branch" 2>/dev/null; then
log_success "$branch_type 验证通过: $branch"
return 0
else
log_error "$branch_type $branch 不存在"
log_info "可用的分支:"
git branch -a 2>/dev/null | head -10 | while read -r line; do
log_info " $line"
done
log_info ""
log_info "解决方案:"
log_info " 1. 手动指定远程分支: BASE_BRANCH=origin/your-branch ./diff_cover_venv.sh"
log_info " 2. 拉取远程分支: git fetch origin"
log_info " 3. 检查远程分支是否存在: git branch -r"
exit 1
fi
}
# ===== 自动检测远程分支 =====detect_base_branch() {
# 如果用户已经指定了基准分支,直接使用
if [-n "$BASE_BRANCH"](); then
log_info "使用用户指定的基准分支: $BASE_BRANCH"
validate_branch "$BASE_BRANCH" "基准分支"
return 0
fi
# 如果设置了跳过远程分支比对,使用当前分支
if ["$SKIP_BRANCH_CHECK" == "1"](); then
log_info "跳过远程分支比对,使用当前分支作为基准"
BASE_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "main")
log_success "使用当前分支作为基准: $BASE_BRANCH"
return 0
fi
log_info "自动检测远程基准分支..."
# 检查是否有远程仓库配置
if ! git remote get-url origin >/dev/null 2>&1; then
log_error "未配置远程仓库 origin,请检查 Git 配置"
log_info "解决方案:"
log_info " 1. 检查是否在 Git 仓库中: git status"
log_info " 2. 添加远程仓库: git remote add origin <仓库URL>"
exit 1
fi
# 只从远程获取默认分支,不允许降级到本地分支
local default_branch=""
if ! git fetch origin >/dev/null 2>&1; then
log_error "无法连接到远程仓库 origin" log_info "解决方案:"
log_info " 1. 检查网络连接"
log_info " 2. 检查远程仓库配置: git remote -v"
log_info " 3. 手动指定远程分支: BASE_BRANCH=origin/main ./diff_cover_venv.sh"
exit 1
fi
# 尝试多种方式获取远程默认分支
default_branch=$(git remote show origin | sed -n '/HEAD branch/s/.*: //p' 2>/dev/null)
# 如果上面失败,尝试从远程分支列表获取
if [-z "$default_branch"](); then
default_branch=$(git ls-remote --symref origin HEAD | sed -n 's/.*refs\/heads\/\([^[:space:]]*\).*/\1/p' 2>/dev/null)
fi
# 如果还是失败,尝试常见的默认分支名
if [-z "$default_branch"](); then
for branch in main master develop; do
if git ls-remote --heads origin "$branch" | grep -q "$branch"; then
default_branch="$branch"
log_info "通过远程分支检测到默认分支: $branch"
break
fi
done fi # 如果仍然找不到远程分支,报错
if [-z "$default_branch"](); then
log_error "无法检测到远程默认分支"
log_info "可用的远程分支:"
git ls-remote --heads origin 2>/dev/null | sed 's/.*refs\/heads\///' | head -10 | while read -r branch; do
log_info " origin/$branch"
done
log_info ""
log_info "解决方案:"
log_info " 1. 手动指定远程分支: BASE_BRANCH=origin/main ./diff_cover_venv.sh"
log_info " 2. 检查远程仓库是否有默认分支"
exit 1
fi
# 设置基准分支 - 只使用远程分支
if ["$default_branch" == *"origin/"*](); then
BASE_BRANCH="$default_branch"
else
# 只使用远程分支,不允许降级到本地分支
BASE_BRANCH="origin/$default_branch"
fi
log_success "检测到基准分支: $BASE_BRANCH"
# 最终验证远程分支是否存在
validate_branch "$BASE_BRANCH" "基准分支"
}
# ===== 检查分支完整性 =====check_branch_integrity() {
# 如果设置了跳过远程分支比对,也跳过分支完整性检查
if ["$SKIP_BRANCH_CHECK" == "1"](); then
log_info "跳过分支完整性检查"
return 0
fi
log_info "检查当前分支是否包含基准分支的所有提交..."
# 获取当前分支名
local current_branch
current_branch=$(git rev-parse --abbrev-ref HEAD)
# 检查当前分支是否包含基准分支的所有提交
if git merge-base --is-ancestor "$BASE_BRANCH" "$current_branch" 2>/dev/null; then
log_success "当前分支 $current_branch 包含基准分支 $BASE_BRANCH 的所有提交"
else
log_warning "当前分支 $current_branch 不包含基准分支 $BASE_BRANCH 的所有提交"
log_warning "这可能导致增量覆盖率分析不准确"
# 显示缺失的提交数量
local missing_commits
missing_commits=$(git rev-list --count "$BASE_BRANCH" ^"$current_branch" 2>/dev/null || echo "未知")
log_warning "基准分支领先当前分支 $missing_commits 个提交"
# 询问是否继续
if ["${FORCE_ANALYSIS:-0}" != "1"](); then
log_warning "建议先合并基准分支或使用 --force 参数强制分析"
log_warning "设置环境变量 FORCE_ANALYSIS=1 可以跳过此检查"
exit 1
else
log_warning "已设置 FORCE_ANALYSIS=1,继续分析..."
fi
fi}
# ===== 自动检测模块 =====detect_modules() {
if [-n "$MODULES"](); then
log_info "使用指定的模块: $MODULES"
return 0
fi
log_info "自动检测项目模块..."
# 检测 Gradle 项目模块
if [-f "settings.gradle"](); then
local gradle_modules
gradle_modules=$(grep "include " settings.gradle | sed "s/.*include ['\"]//g" | sed "s/['\"].*//g" | tr '\n' ',' | sed 's/,$//')
if [-n "$gradle_modules"](); then
MODULES="$gradle_modules"
log_success "检测到 Gradle 模块: $MODULES"
return 0
fi
fi # 检测 Maven 项目模块
if [-f "pom.xml"](); then
local maven_modules
maven_modules=$(grep -E "<module>" pom.xml | sed "s/.*<module>//g" | sed "s/<\/module>.*//g" | tr '\n' ',' | sed 's/,$//')
if [-n "$maven_modules"](); then
MODULES="$maven_modules"
log_success "检测到 Maven 模块: $MODULES"
return 0
fi
fi # 默认模块检测(基于目录结构)
local detected_modules=""
for dir in */; do
if [-d "${dir}src/main/java"]() || [-d "${dir}src/main/kotlin"](); then
local module_name="${dir%/}"
if [-z "$detected_modules"](); then
detected_modules="$module_name"
else
detected_modules="$detected_modules,$module_name"
fi
fi done if [-n "$detected_modules"](); then
MODULES="$detected_modules"
log_success "检测到模块: $MODULES"
else
log_error "未检测到模块,请手动指定 MODULES 环境变量"
exit 1
fi
}
# ===== 检查覆盖率报告 =====check_coverage_report() {
local module="$1"
if [$SKIP_TESTS -eq 1](); then
log_info "[$module] 跳过测试执行检查,直接使用现有报告..."
else
log_info "[$module] 检查覆盖率报告..."
log_warning "请确保已运行测试并生成了 Jacoco 报告"
log_warning "例如: mvn test jacoco:report -pl $module"
fi
local coverage_xml="$module/target/jacoco/jacoco.xml"
if [ ! -f "$coverage_xml" ]; then
log_error "未找到 Jacoco 报告: $coverage_xml"
log_error "请先运行测试生成覆盖率报告"
return 1
fi
log_success "找到 Jacoco 报告: $coverage_xml"
echo "$coverage_xml"
}
# ===== 转换Jacoco XML为Cobertura XML =====
convert_jacoco_to_cobertura() {
local jacoco_xml="$1"
local cobertura_xml="$2"
log_info "转换 Jacoco XML 为 Cobertura XML 格式..."
# 创建临时转换脚本
local script_file="/tmp/jacoco_to_cobertura_$$.py"
cat > "$script_file" << 'EOF'
#!/usr/bin/env python3
import xml.etree.ElementTree as ET
import sys
import os
import time
def convert_jacoco_to_cobertura(jacoco_file, cobertura_file):
try: tree = ET.parse(jacoco_file) root = tree.getroot() coverage = ET.Element('coverage')
coverage.set('line-rate', '0.0') coverage.set('branch-rate', '0.0') coverage.set('lines-covered', '0') coverage.set('lines-valid', '0') coverage.set('branches-covered', '0') coverage.set('branches-valid', '0') coverage.set('complexity', '0.0') coverage.set('version', '1.9') coverage.set('timestamp', str(int(time.time()))) sources = ET.SubElement(coverage, 'sources')
source = ET.SubElement(sources, 'source') # 动态获取模块名和源码路径
jacoco_dir = os.path.dirname(jacoco_file) # 从 jacoco.xml 路径推导模块路径: hinton-application/target/jacoco/jacoco.xml -> hinton-application
module_name = os.path.basename(os.path.dirname(os.path.dirname(jacoco_dir))) source.text = os.path.join(os.getcwd(), module_name, 'src', 'main', 'java') packages = ET.SubElement(coverage, 'packages')
total_lines_covered = 0 total_lines_valid = 0 for package in root.findall('.//package'):
pkg_name = package.get('name', '') pkg_lines_covered = 0 pkg_lines_valid = 0 pkg_elem = ET.SubElement(packages, 'package')
pkg_elem.set('name', pkg_name) pkg_elem.set('line-rate', '0.0') pkg_elem.set('branch-rate', '0.0') pkg_elem.set('complexity', '0.0') classes = ET.SubElement(pkg_elem, 'classes')
for sourcefile in package.findall('.//sourcefile'):
class_name = sourcefile.get('name', '').replace('.java', '') class_lines_covered = 0 class_lines_valid = 0 class_elem = ET.SubElement(classes, 'class')
class_elem.set('name', class_name) class_elem.set('filename', f"{pkg_name.replace('.', '/')}/{sourcefile.get('name', '')}") class_elem.set('line-rate', '0.0') class_elem.set('branch-rate', '0.0') class_elem.set('complexity', '0.0') lines = ET.SubElement(class_elem, 'lines')
for line in sourcefile.findall('.//line'):
line_num = line.get('nr', '0') line_hits = int(line.get('ci', '0')) line_elem = ET.SubElement(lines, 'line')
line_elem.set('number', line_num) line_elem.set('hits', str(line_hits)) line_elem.set('branch', 'false') class_lines_valid += 1
if line_hits > 0: class_lines_covered += 1 if class_lines_valid > 0:
class_rate = class_lines_covered / class_lines_valid class_elem.set('line-rate', f"{class_rate:.4f}") pkg_lines_covered += class_lines_covered
pkg_lines_valid += class_lines_valid if pkg_lines_valid > 0:
pkg_rate = pkg_lines_covered / pkg_lines_valid pkg_elem.set('line-rate', f"{pkg_rate:.4f}") total_lines_covered += pkg_lines_covered
total_lines_valid += pkg_lines_valid if total_lines_valid > 0:
total_rate = total_lines_covered / total_lines_valid coverage.set('line-rate', f"{total_rate:.4f}") coverage.set('lines-covered', str(total_lines_covered)) coverage.set('lines-valid', str(total_lines_valid)) tree = ET.ElementTree(coverage)
ET.indent(tree, space=" ", level=0) tree.write(cobertura_file, encoding='utf-8', xml_declaration=True) return True except Exception as e:
print(f"转换失败: {e}", file=sys.stderr)
return False
if __name__ == "__main__":
if len(sys.argv) != 3: print("用法: python3 jacoco_to_cobertura.py <jacoco.xml> <cobertura.xml>", file=sys.stderr)
sys.exit(1) if convert_jacoco_to_cobertura(sys.argv[1], sys.argv[2]):
print(f"转换成功: {sys.argv[2]}")
sys.exit(0) else: sys.exit(1)EOF
# 执行转换
if python "$script_file" "$jacoco_xml" "$cobertura_xml"; then
log_success "转换完成: $cobertura_xml"
rm -f "$script_file"
return 0
else
log_error "转换失败"
rm -f "$script_file"
return 1
fi
}
# ===== 使用diff-cover分析增量覆盖率 =====analyze_diff_coverage() {
local module="$1"
local cobertura_xml="$2"
log_info "[$module] 使用 diff-cover 分析增量覆盖率..."
# 创建报告目录(如果不存在)
[! -d "$OUTPUT_DIR"]() && mkdir -p "$OUTPUT_DIR"
local html_report="$OUTPUT_DIR/${module}-diff-cover-report.html"
local json_report="$OUTPUT_DIR/${module}-diff-cover-report.json"
# 执行diff-cover,指定源码根目录以正确匹配路径
# 添加更多报告选项以增强覆盖情况展示
local diff_output
local diff_cmd="diff-cover \"$cobertura_xml\""
# 如果设置了跳过远程分支比对,不进行分支比对
if ["$SKIP_BRANCH_CHECK" == "1"](); then
log_info "[$module] 跳过分支比对,进行整体覆盖率分析"
diff_cmd="$diff_cmd --src-roots \"$module/src/main/java\""
else
diff_cmd="$diff_cmd --compare-branch \"$BASE_BRANCH\" --src-roots \"$module/src/main/java\""
fi
# 添加调试信息
log_info "[$module] 执行命令: $diff_cmd"
log_info "[$module] HTML报告路径: $html_report"
log_info "[$module] JSON报告路径: $json_report"
if diff_output=$(eval "$diff_cmd \ --html-report \"$html_report\" \
--json-report \"$json_report\" \
--markdown-report \"$OUTPUT_DIR/${module}-diff-cover-report.md\" \
--show-uncovered \ --fail-under 0" 2>&1); then
log_success "[$module] diff-cover 分析完成"
log_info "报告文件:"
# 检查报告文件是否真的被创建
if [-f "$html_report"](); then
log_info " - HTML: $html_report ✅"
else
log_warning " - HTML: $html_report ❌ (文件未创建)"
fi
if [-f "$json_report"](); then
log_info " - JSON: $json_report ✅"
else
log_warning " - JSON: $json_report ❌ (文件未创建)"
fi
if [-f "$OUTPUT_DIR/${module}-diff-cover-report.md"](); then
log_info " - Markdown: $OUTPUT_DIR/${module}-diff-cover-report.md ✅"
else
log_warning " - Markdown: $OUTPUT_DIR/${module}-diff-cover-report.md ❌ (文件未创建)"
fi
# 显示详细的覆盖率信息
echo ""
echo "========================================"
echo "📊 [$module] 增量覆盖率详情"
echo "========================================"
# 提取并显示覆盖率摘要信息
local coverage_summary=$(echo "$diff_output" | sed -n '/^Total:/,/^Coverage:/p')
if [-n "$coverage_summary"](); then
echo "$coverage_summary"
fi
# 显示文件级别的覆盖率信息
local file_coverage=$(echo "$diff_output" | grep -E "\.java \([0-9.]+%\):" | head -10)
if [-n "$file_coverage"](); then
echo ""
echo "📁 文件覆盖率详情:"
echo "$file_coverage" | while read -r line; do
echo " $line"
done
# 如果文件超过10个,显示省略信息
local total_files=$(echo "$diff_output" | grep -c "\.java \([0-9.]+%\):")
if [$total_files -gt 10](); then
echo " ... 还有 $((total_files - 10)) 个文件"
fi
fi echo "========================================"
echo ""
# 从控制台输出中解析覆盖率
local coverage_percent=$(echo "$diff_output" | grep "Coverage:" | sed 's/.*Coverage: \([0-9.]*\)%.*/\1/')
local total_lines=$(echo "$diff_output" | grep "Total:" | sed 's/.*Total: *\([0-9]*\) lines.*/\1/')
local missing_lines=$(echo "$diff_output" | grep "Missing:" | sed 's/.*Missing: *\([0-9]*\) lines.*/\1/')
# 调试信息(可选,用于排查问题)
# log_info "[$module] 调试 - 原始输出片段:"
# echo "$diff_output" | grep -E "(Total:|Missing:|Coverage:)" | head -3 | while read -r line; do # log_info "[$module] 调试 - $line" # done if [-z "$coverage_percent"](); then
# 如果没有找到覆盖率信息,检查是否有"No lines with coverage information"
if echo "$diff_output" | grep -q "No lines with coverage information"; then
log_info "[$module] 没有检测到需要覆盖的代码行"
COVERAGE_INFO="0,0,0" # 设置 coverage_percent,total_lines,missing_lines return 0
else
log_warning "[$module] 无法解析覆盖率信息"
COVERAGE_INFO="0,0,0" # 设置 coverage_percent,total_lines,missing_lines return 0
fi
fi # 将覆盖率信息写入全局变量供主函数使用
COVERAGE_INFO="${coverage_percent},${total_lines:-0},${missing_lines:-0}"
if (( $(echo "$coverage_percent < $THRESHOLD" | bc -l) )); then
log_warning "[$module] 覆盖率 $coverage_percent% 低于阈值 $THRESHOLD%"
return 0 # 阈值不达标只是警告,不返回失败
else
log_success "[$module] 覆盖率 $coverage_percent% 达到阈值 $THRESHOLD%"
return 0
fi
else log_error "[$module] diff-cover 分析失败"
log_error "命令输出: $diff_output"
return 1
fi
}
# ===== 显示使用帮助 =====show_help() {
cat << EOF
合并版本:虚拟环境 + diff-cover 分析脚本
用法:
$0 [选项] [环境变量...]
选项:
--venv-dir DIR 指定虚拟环境目录 (默认: ./temp_venv)
--no-cleanup 执行后不清理虚拟环境,下次运行时复用
--help, -h 显示此帮助信息
环境变量:
MODULES 要分析的模块列表 (默认: hinton-application,hinton-domain)
THRESHOLD 覆盖率阈值 (默认: 100)
OUTPUT_DIR 报告输出目录 (默认: ./reports)
SKIP_TESTS 跳过测试执行检查,使用现有报告 (默认: 0)
FORCE_ANALYSIS 强制分析 (默认: 0)
BASE_BRANCH 基准分支 (默认: 自动检测)
SKIP_BRANCH_CHECK 跳过远程分支比对 (默认: 0)
VENV_DIR 虚拟环境目录路径
CLEANUP_VENV 是否清理虚拟环境 (1=清理, 0=保留)
示例:
$0 # 使用默认设置
$0 --venv-dir ./my_venv # 指定虚拟环境目录
$0 --no-cleanup # 执行后保留虚拟环境
$0 MODULES=hinton-application,hinton-domain THRESHOLD=80 # 设置环境变量
$0 BASE_BRANCH=origin/main # 手动指定基准分支
$0 SKIP_BRANCH_CHECK=1 # 跳过远程分支比对
常见问题解决:
1. 找不到远程分支 origin/main: - 检查网络连接: git fetch origin
- 手动指定远程分支: BASE_BRANCH=origin/main ./diff_cover_venv.sh
- 检查远程仓库配置: git remote -v
2. 未配置远程仓库:
- 添加远程仓库: git remote add origin <仓库URL>
- 手动指定远程分支: BASE_BRANCH=origin/main ./diff_cover_venv.sh
3. 网络连接问题:
- 检查网络连接和代理设置
- 手动指定远程分支: BASE_BRANCH=origin/main ./diff_cover_venv.sh
- 跳过远程分支比对: SKIP_BRANCH_CHECK=1 ./diff_cover_venv.sh
4. 跳过远程分支比对:
- 使用环境变量: SKIP_BRANCH_CHECK=1 ./diff_cover_venv.sh
- 将进行整体覆盖率分析,不进行分支比对
注意:
- 脚本会自动创建临时虚拟环境
- 默认情况下执行完成后会自动清理虚拟环境
- 使用 --no-cleanup 可以保留虚拟环境,下次运行时复用(节省时间)
- 如果自动检测失败,可以手动指定 BASE_BRANCH 环境变量
- 使用 SKIP_BRANCH_CHECK=1 可以跳过远程分支比对,进行整体覆盖率分析
- 虚拟环境复用逻辑:保留时复用,清理时重建
EOF
}
# ===== 解析命令行参数 =====parse_args() {
while [$# -gt 0](); do
case $1 in
--venv-dir)
VENV_DIR="$2"
shift 2
;;
--no-cleanup)
CLEANUP_VENV=0
shift
;;
--help|-h)
show_help
exit 0
;;
*)
# 其他参数作为环境变量处理
break
;;
esac done}
# ===== 主函数 =====main() {
log_info "开始基于虚拟环境的增量覆盖率分析..."
# 解析参数
parse_args "$@"
# 设置退出时清理
trap cleanup_venv EXIT
# 检查环境依赖
check_environment
# 创建临时虚拟环境
create_temp_venv
# 设置虚拟环境
setup_venv
# 检测模块和分支
detect_modules
detect_base_branch
check_branch_integrity
local success_count=0
local total_count=0
local total_covered_lines=0
local total_missing_lines=0
local total_all_lines=0
IFS=',' read -ra MODULE_LIST <<< "$MODULES"
for module in "${MODULE_LIST[@]}"; do
module=$(echo "$module" | xargs)
total_count=$((total_count + 1))
log_info "处理模块: $module"
# 检查覆盖率报告
local coverage_xml
if ! coverage_xml=$(check_coverage_report "$module"); then
log_error "模块 $module 处理失败"
continue
fi
# 转换Jacoco XML为Cobertura XML
local cobertura_xml="$module/target/jacoco/cobertura.xml"
# 确保目标目录存在
[! -d "$(dirname "$cobertura_xml")"]() && mkdir -p "$(dirname "$cobertura_xml")"
if ! convert_jacoco_to_cobertura "$coverage_xml" "$cobertura_xml"; then
log_error "模块 $module XML转换失败"
continue
fi
# 使用diff-cover分析并获取覆盖率信息
if analyze_diff_coverage "$module" "$cobertura_xml"; then
success_count=$((success_count + 1))
# 解析覆盖率信息: coverage_percent,total_lines,missing_lines
IFS=',' read -r coverage_percent total_lines missing_lines <<< "$COVERAGE_INFO"
# 累计统计信息
total_all_lines=$((total_all_lines + total_lines))
total_missing_lines=$((total_missing_lines + missing_lines))
total_covered_lines=$((total_covered_lines + (total_lines - missing_lines)))
log_info "[$module] 覆盖率: ${coverage_percent}% (${total_lines}行总计, ${missing_lines}行缺失)"
else
log_error "模块 $module diff-cover分析失败"
fi
done # 总结
echo ""
echo "========================================"
echo "📊 分析完成"
echo "========================================"
echo "成功处理模块: $success_count/$total_count"
echo "报告目录: $OUTPUT_DIR"
# 计算合并的覆盖率
if [ $total_all_lines -gt 0 ]; then
local overall_coverage
overall_coverage=$(echo "scale=2; $total_covered_lines * 100 / $total_all_lines" | bc -l)
echo ""
echo "========================================"
echo "🎯 合并覆盖率统计"
echo "========================================"
echo "总代码行数: $total_all_lines"
echo "已覆盖行数: $total_covered_lines"
echo "缺失行数: $total_missing_lines"
echo "合并覆盖率: ${overall_coverage}%"
# 检查是否达到阈值
if (( $(echo "$overall_coverage < $THRESHOLD" | bc -l) )); then
log_warning "合并覆盖率 ${overall_coverage}% 低于阈值 ${THRESHOLD}%"
else
log_success "合并覆盖率 ${overall_coverage}% 达到阈值 ${THRESHOLD}%"
fi
else log_warning "没有检测到需要覆盖的代码行"
fi
if [ $success_count -eq $total_count ]; then
log_success "所有模块分析完成"
exit 0
else
log_error "部分模块分析失败"
exit 1
fi
}
# ===== 脚本入口 =====main "$@"